from google.appengine.ext import db
from shipwreck.main.helpers import *
from shipwreck.main.data import *
import logging
from datetime import datetime

class IdBasedDomainObject(db.Model):
    number = db.IntegerProperty()
    name = db.StringProperty(required=True)
    
class Zone(IdBasedDomainObject):
    x = db.IntegerProperty()
    y = db.IntegerProperty()
    width = db.IntegerProperty(required=True)
    height= db.IntegerProperty(required=True)
    map = db.ListProperty(long)
    def __str__(self):
        return "Zone: %d '%s'" % (self.number, self.name)
    def getLocalCoordinates(self, (x,y)):
        x,y = int(x),int(y)
        sx,sy = self.x,self.y
        tx,ty = (sx + self.width, sy + self.height)
        if x > sx and x < tx and y > sy and y < ty:
            return x-sx, y-sy
        return None
    def getTerrainAtGlobal(self,(x,y)):
        return self.getTerrainAt(self.getLocalCoordinates((x,y)))
    def getTerrainAt(self,(x,y)):
        return terrain[self.map[y*self.width+x]]
    @staticmethod
    def getZoneForLocation(coordinate):
        (x,y) = self.coordinatesFromString(coordinate)
        for zone in Zone.all().fetch(100):
            (zx,zy) = self.coordinatesFromString(zone.coords)
            if zx+zone.width > x > zx and zy+zone.height > y > zy:
                return zone
    def toHtml(self):
        output = "<table>"
        for line in range(self.height):
            output += "<tr>"
            for column in range(self.width):
                output += "<td>%s</td>" % (self.getTerrainAt((column,line)).symbol)
            output += "</tr>"
        output += "</table>"
        return output
        
class Fightable(IdBasedDomainObject):
    strength = db.IntegerProperty(required=True, default=5)
    agility = db.IntegerProperty(required=True, default=5)
    endurance = db.IntegerProperty(required=True, default=5)

    # Skills
    #combat
    skill_fighting = db.IntegerProperty(required=True, default=0)
    skill_dodging = db.IntegerProperty(required=True, default=0)
    #environment
    skill_hunting = db.IntegerProperty(required=True, default=0)
    skill_hiding = db.IntegerProperty(required=True, default=0)
    # Other Attributes
    health = db.IntegerProperty(required=True, default=5)
    max_health = db.IntegerProperty(required=True, default=5)
    armour = db.IntegerProperty(required=True, default=0)
    
    def calculateBonuses(self, primeAttribute, secondaryAttribute):
        return ((1/2)*primeAttribute)+((1/4)*secondaryAttribute)
    
    def calculateInitiative(self):
        return random.randint(1,6)+self.calculateBonuses(self.agility, 0)
    def hit(self, target):
        if self.health < 1:
            return None
        attack = random.randint(1,6)+self.calculateBonuses(self.strength, self.skill_fighting)
        defence = random.randint(1,6)+target.calculateBonuses(target.agility, target.skill_dodging)
        if attack >= defence:
            damage = 1 + random.randint(0,self.calculateBonuses(self.strength, self.skill_fighting))
            target.health -= damage
            return "%s hit %s for %d damage" % (self.name, target.name, damage)
        else:
            return "%s misses" % (self.name)
    def battle(self, monster):
        battlenum = getNextNumberFor(BattleRound)
        my_initiative = self.calculateInitiative()
        mon_initiative = monster.calculateInitiative()
        rounds = []
        for r in range(20):
            round = BattleRound(char=self, round=r, number=battlenum)
            if my_initiative >= mon_initiative:
                round.action1 = self.hit(monster)
                round.action2 = monster.hit(self)
            else:
                round.action1 = self.hit(monster)
                round.action2 = monster.hit(self)
            if (self.health < 1):
                round.status = "You lost the battle, you crawl away to recuperate"
            if (monster.health < 1):
                round.status = "You killed the %s" % (monster.name)
            round.put()
            rounds.append(round)
            if self.health < 1 or monster.health < 1:
                break
        self.put()
        return rounds
        
class Character(Fightable):
    #Non Game Attributes
    owner = db.UserProperty()
    sex = db.StringProperty(required=True, choices=('Male', 'Female'), default='Male')
    # Attributes
    # References
    x = db.IntegerProperty()
    y = db.IntegerProperty()
    zone = db.ReferenceProperty(Zone)
    visited = db.StringListProperty()
    actions = db.IntegerProperty(required=True, default=20)
    last_action_time = db.DateTimeProperty()
    def ActionPointsDue(self, now, last):
        td = now - last
        logging.info("Character last recalculated action points at %s (now is %s) which is %d days, %d seconds ago" % (last,now,td.days, td.seconds))
        minutes, seconds = divmod(td.seconds, 60)
        return ((td.days * 1440) + (minutes)) / 30
    def calculateVisited(self):
        zx,zy = self.zone.x,self.zone.y
        for dy in range(-1,2):
            for dx in range(-1,2):
                (nx,ny) = (self.x + dx - zx, self.y+dy-zy)
                if nx < self.zone.width and ny < self.zone.height \
                    and nx > 0 and ny > 0:
                    coords = "%d,%d,%d" % (nx,ny,self.zone.number)
                    if not coords in self.visited:
                        self.visited.append(coords) 
        
    def calculateActions(self):
        self.calculateVisited()
        last_time = self.last_action_time
        now = datetime.utcnow()
        if last_time: 
            c_points = self.ActionPointsDue(now, self.last_action_time)
            logging.info("Old action points: %d, gained %d" % (self.actions,c_points))
            if c_points > 0:
                self.actions += c_points
                self.last_action_time = now
                self.put()
        else:
            self.last_action_time = now
            self.put()
    def moveto(self, zone, x, y, terrain):
        self.each_turn()
        self.actions -= terrain.cost
        self.zone = zone
        self.x = x
        self.y = y
        self.put()

    def each_turn(self):
        if self.health < self.max_health:
            self.health += 1
            
    def wait(self):
        self.each_turn()
        self.actions -= 1
        self.put()

    def setRandomStartingPosition(self, zone):
        while True:
            x = random.randint(zone.x+2, zone.x+zone.width-2)
            logging.info("Searching for land masses at %d" % (x))
            for y in range(15):
                t = zone.getTerrainAt((x,y))
                if t.passable:
                    self.x = x
                    self.y = y
                    self.zone = zone
                    return

    def surroundings_as_html(self, size_x=3, size_y=3):
        output = "<table class=\"map\">\n"
        zone = self.zone
        zx,zy = zone.x,zone.y
        for dy in range(size_y*-1,size_y+1):
            output += "<tr>\n"
            for dx in range(size_x*-1,size_x+1):
                (nx,ny) = (self.x + dx - zx, self.y+dy-zy)
                if nx < zone.width and ny < zone.height \
                    and nx > 0 and ny > 0:
                    if "%d,%d,%d" % (nx,ny,zone.number) in self.visited:
                        terrain = zone.getTerrainAt((nx,ny))
                    else:
                        terrain = Terrain(-1,"Unknown", "", "-1.png", False, 0)
                    image_src = "<img class=\"terrain terrain-%s\" src=\"/images/terrain/%s\"/>" % (terrain.name, terrain.filename)
                    characters = Character.all().filter('x =',nx).filter('y =',ny).fetch(10)
                    if len(characters) > 0:
                        output += "<td class=\"terrain-tr charhere\">"
                    else:
                        output += "<td class=\"terrain-tr\">"
                    
                    if abs(dx) <= 1 and abs(dy) <= 1:
                        output += makeMovementLink(image_src, zone, self.x+dx+zx, self.y+dy+zy)
                    else:
                        output += image_src
                    output += "</td>\n"
            output += "</tr>\n"
        output += "</table>\n"
        return output

    
class ItemPrototype(IdBasedDomainObject):
    type = db.IntegerProperty(choices=itemlist.keys(), required=True)
    value = db.IntegerProperty(required=True)
    primary_skill = db.IntegerProperty(choices=skilllist.keys())
    secondary_skill = db.IntegerProperty(choices=skilllist.keys())
    uses = db.IntegerProperty()
    primary_bonus = db.IntegerProperty()
    secondary_bonus = db.IntegerProperty()
    
class CreaturePrototype(Fightable):
    challenge_level = db.IntegerProperty(required=True)
    zones = db.ListProperty(long)
    terrain = db.ListProperty(long)
    # Attributes    
    def __str__(self):
        return "Creature %d: %s (%dhp) found in %s, terrain %s" % (self.number, self.name, self.health, str(self.zones), str(self.terrain))
    @staticmethod
    def findRandomCreatureForLocation((x,y),zone):
        # Get a list of possible creatures for this zone / terrain
        logging.info("Checking for creatures that live in zone %d" % (zone.number))
        creatures = CreaturePrototype.all().filter("zones =",zone.number).filter("terrain =",zone.getTerrainAtGlobal((x,y)).number).fetch(100)
        if creatures and len(creatures) > 0:
            return random.choice(creatures)
        return None
    
class BattleRound(db.Model):
    number = db.IntegerProperty(required=True)
    char = db.ReferenceProperty(Character)
    round = db.IntegerProperty()
    action1 = db.StringProperty()
    action2 = db.StringProperty()
